﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using System.Data.SqlClient;
using System.Globalization;
using System.Runtime.Serialization;

namespace BlueLotus7.Common
{
    public class Logger : IDisposable
    {
        #region Fields

        private EventLog eventLog;
        private StreamWriter logFileWriter;
        static private SerializeUtility serializer = new SerializeUtility();

        private bool useEventLog;
        private bool usingDayTimeMode;

        private string applicationName;
        private string logFileFolder;
        private string logFilePath;
        private string sourceName;

        private int successCount = 0;
        private int errorCount = 0;

        public int SuccessCount { get { return successCount; } }
        public int ErrorCount { get { return errorCount; } }

        #endregion

        public Logger(string applicationName, bool usingDaytimeMode, bool useEventLog, string sourceName)
        {
            this.applicationName = applicationName;
            this.logFileFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");

            this.useEventLog = useEventLog;
            this.sourceName = sourceName;

            this.usingDayTimeMode = usingDaytimeMode;
            Initialize();
        }

        public Logger(string applicationName, string logFileFolder, bool usingDaytimeMode, bool useEventLog, string sourceName)
        {
            this.applicationName = applicationName;
            this.logFileFolder = logFileFolder;

            this.useEventLog = useEventLog;
            this.sourceName = sourceName;

            this.usingDayTimeMode = usingDaytimeMode;
            Initialize();
        }

        private void Initialize()
        {
            if (string.IsNullOrWhiteSpace(sourceName))
            {
                sourceName = AppDomain.CurrentDomain.ApplicationIdentity.FullName;
                if (string.IsNullOrWhiteSpace(sourceName))
                {
                    sourceName = AppDomain.CurrentDomain.FriendlyName;
                }
            }
            if (string.IsNullOrWhiteSpace(applicationName))
            {
                applicationName = sourceName;
            }

            if (useEventLog)
            {
                if (!EventLog.SourceExists(sourceName))
                {
                    EventLog.CreateEventSource(sourceName, applicationName);
                    eventLog = new EventLog(applicationName);
                    eventLog.Source = sourceName;

                    eventLog.WriteEntry("Event log created", EventLogEntryType.Information);
                }
                else
                {
                    eventLog = new EventLog(applicationName);
                    eventLog.Source = sourceName;
                }
            }


            if (usingDayTimeMode)
            {
                logFilePath = GenerateDaytimeFilePath();
            }
            else if (string.IsNullOrWhiteSpace(logFileFolder))
            {
                logFilePath = Path.Combine(CurrentDictionary, applicationName);
            }
            else
            {
                logFilePath = Path.Combine(logFileFolder, applicationName);
            }

            if (!Directory.Exists(logFileFolder))
            {
                Directory.CreateDirectory(logFileFolder);
            }

            if (File.Exists(logFilePath))
            {
                logFileWriter = new StreamWriter(logFilePath, true);
            }
            else
            {
                logFileWriter = new StreamWriter(logFilePath);
            }
        }

        public void IncrementSuccessCount(int count)
        {
            successCount += count;
        }

        public void IncrementErrorCount(int count)
        {
            errorCount += count;
        }

        public void LogMessage(string msg)
        {
            if (usingDayTimeMode)
            {
                string newFilePath = GenerateDaytimeFilePath();
                if (newFilePath != logFilePath)
                {
                    logFileWriter.Close();
                    logFilePath = newFilePath;
                    if (File.Exists(logFilePath))
                    {
                        logFileWriter = new StreamWriter(logFilePath, true);
                    }
                    else
                    {
                        logFileWriter = new StreamWriter(logFilePath);
                    }
                }
            }
            try
            {
                logFileWriter.WriteLine(msg);
                logFileWriter.Flush();
            }
            catch (Exception ex)
            {
                if (useEventLog)
                {
                    LogEvent(EventLogEntryType.Error, "Error writing to log file", ex);
                }
            }
        }

        public void LogMessageWithTimestamp(string msg)
        {
            if (!string.IsNullOrWhiteSpace(msg))
            {
                LogMessage(string.Format(CultureInfo.InvariantCulture, "[{0}] {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), msg));
            }
        }

        public void LogBlankLine()
        {
            LogMessage(string.Empty);
        }

        public void LogError(string msg)
        {
            errorCount++;
            LogBlankLine();
            LogMessage("************************");
            LogMessage("***      ERROR       ***");
            LogMessage(msg);
            LogMessage("************************");
            LogBlankLine();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public void LogException(Exception ex)
        {
            LogException(ex, null);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public void LogException(Exception ex, object data)
        {
            string msg = string.Format("[{0}] {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), FormatException(ex, data));
            LogError(msg);
        }

        public void LogException(string msg, Exception ex)
        {
            LogError(String.Format(CultureInfo.InvariantCulture, "{0}\r\n\r\n{1}", msg, FormatException(ex)));
            if (useEventLog)
            {
                LogEvent(EventLogEntryType.Error, msg, ex);
            }
        }

        public void LogException(string msg, Exception ex, object data)
        {
            LogException(msg, ex, null);
        }

        public void LogException(int code, Exception ex)
        {
            LogException(code, ex, null);
        }

        public void LogException(int code, Exception ex, object data)
        {
            LogError(String.Format(CultureInfo.InvariantCulture, "Code:{0}\r\n\r\n{1}", code, FormatException(ex, data)));
            if (useEventLog)
            {
                LogEvent(code, EventLogEntryType.Error, null, ex);
            }
        }

        public void LogException(int code, string msg, Exception ex)
        {
            LogError(String.Format(CultureInfo.InvariantCulture, "Code:{0}\r\n\r\n{1}\r\n\r\n{2}", code, msg, FormatException(ex)));
            if (useEventLog)
            {
                LogEvent(code, EventLogEntryType.Error, msg, ex);
            }
        }

        public void LogEvent(EventLogEntryType type, string msg)
        {
            LogEvent(0, type, msg, null);
        }

        public void LogEvent(EventLogEntryType type, string msg, Exception ex)
        {
            LogEvent(0, type, msg, ex);
        }

        public void LogEvent(int code, EventLogEntryType type, string msg)
        {
            LogEvent(code, type, msg, null);
        }

        public void LogEvent(int code, EventLogEntryType type, string msg, Exception ex)
        {
            LogEvent(code, type, msg, ex, null);
        }

        public void LogEvent(int code, EventLogEntryType type, string msg, Exception ex, object data)
        {
            if (useEventLog)
            {
                string fullMsg;

                try
                {
                    fullMsg = String.Concat(msg, ex != null ? String.Concat("\r\n\r\n", FormatException(ex, data)) : "");
                    eventLog.WriteEntry(fullMsg, type, code);
                }
                catch
                {
                    //unable to log event to event log so there is nothing else that we can do to log this error 
                    //so we will just continue
                }
            }
        }

        #region Private methods

        private string CurrentDictionary
        {
            get
            {
                return Assembly.GetExecutingAssembly().Location;
            }
        }

        private string GenerateDaytimeFilePath()
        {
            return Path.Combine(logFileFolder, this.applicationName + "_" + DateTime.Now.ToString("yyyyMMdd") + ".log");
        }

        private void CloseLogFile()
        {
            try
            {
                if (logFileWriter != null)
                {
                    logFileWriter.Flush();
                    logFileWriter.Close();
                }
            }
            catch (Exception ex)
            {
                if (useEventLog)
                {
                    LogEvent(EventLogEntryType.Error, "Unable to close the log file", ex);
                }
            }
        }

        private static string FormatException(Exception ex)
        {
            return FormatException(ex, null);
        }

        private static string FormatException(Exception ex, object obj)
        {
            StringBuilder exDetails = new StringBuilder();

            exDetails.AppendFormat(CultureInfo.InvariantCulture, "Exception Type: {0}\r\n", ex.GetType().ToString());

            SqlException sqlEx = ex as SqlException;
            if (sqlEx != null)
            {
                int i = 0;

                exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n Errors.Count = {0}", sqlEx.Errors.Count);
                foreach (SqlError sqlE in sqlEx.Errors)
                {
                    i++;
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ********** Error #{0} **********", i);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->Class: {0}", sqlE.Class);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->Number: {0}", sqlE.Number);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->Server: {0}", sqlE.Server);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->Source: {0}", sqlE.Source);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->Procedure: {0}", sqlE.Procedure);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->LineNumber: {0}", sqlE.LineNumber);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->State: {0}", sqlE.State);
                    exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n ->Message: {0}", sqlE.Message);
                }
            }

            exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\nException Message: {0}", ex.Message);
            exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n\r\nSource: {0}", ex.Source);
            exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\nSite: {0}", ex.TargetSite);
            exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\nStackTrace: {0}\r\n", ex.StackTrace);
            exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\nData Reference: {0}\r\n", GenerateDataString(obj));

            if (ex.InnerException != null)
                exDetails.AppendFormat(CultureInfo.InvariantCulture, "\r\n***** Inner Exception *****\r\n{0}", FormatException(ex.InnerException));

            return exDetails.ToString();
        }

        private static string GenerateDataString(object obj)
        {
            string result = "<null>";

            if (obj != null)
            {
                result = serializer.SerializeToXml(obj);
            }

            return result;
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            CloseLogFile();
        }

        #endregion
    }
}
